
#include<sys/stat.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<string.h>
#include<asm/types.h>
#include<linux/netlink.h>
#include<linux/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>

#include <linux/netfilter.h>

#include<net/if.h>
#include <netinet/ip.h>
#include <netinet/udp.h>


#include "defs.h"
#include "aodv_neighbor.h"
#include "debug.h"
#include "typecast_params.h"
#include "typecast_socket.h"
#include "type_announce.h"
#include "LBF_update.h"
#include "util.h"
#include "type_routingtable.h"

//typedef void (*callback_func_t) (int);
extern int attach_callback_func(int fd, callback_func_t func);


#define MAX_BUFFER_SIZE 55000

static int typecast_TA_sock = -1; 
static int typecast_LBF_sock = -1; 

//////////////////////////////////////////////////////////////////////////
// ConstructTASockets
//   Initializes the sockets we need for receiving.
//   Returns 0 on success.  Returns nonzero on failure.
static int ConstructTASockets();

//////////////////////////////////////////////////////////////////////////
// ConstructLBFSockets
//   Initializes the sockets we need for updating the LBF.
//   Returns 0 on success.  Returns nonzero on failure.
static int ConstructLBFSockets();


static void ReceiveTypeAnnounce(int fd); 
static void  UpdateLBF(int fd); 
static int SendTypeAnnounce();

/* Simple function (based on R. Stevens) to calculate UDP header checksum */
static inline u_int16_t in_udp_csum(unsigned long saddr,
                                    unsigned long daddr,
                                    unsigned short len,
                                    unsigned short proto,
                                    unsigned short *buf)
{
    unsigned short *w;
    int nleft = ntohs(len);
    int sum = 0;
    struct {
        unsigned short zero:8;
        unsigned short proto:8;
    } proto2 = {0, proto};

    w = (unsigned short *)&saddr;
    sum += *w++;
    sum += *w;

    w = (unsigned short *)&daddr;
    sum += *w++;
    sum += *w;

    sum += *(unsigned short *)&proto2;
    sum += len;

    w = buf;

    while(nleft > 1) {
        sum += *w++;
        nleft -= 2;
    }

    if (nleft == 1)
        sum += *(unsigned char*)w;

    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);          

    return ~sum;

}




void typecast_socket_init()
{
    typecast_TA_sock = ConstructTASockets(); 
    typecast_LBF_sock = ConstructLBFSockets();                     
    
    int retval = attach_callback_func(typecast_TA_sock, ReceiveTypeAnnounce);
    if (retval < 0) {
        perror("register input handler failed ");
        exit(-1);
    }
    retval = attach_callback_func(typecast_LBF_sock, UpdateLBF);
    if (retval < 0) {
        perror("register input handler failed ");
        exit(-1);
    }
            
}


int ConstructTASockets()
{
    // Initialize socket
    int tos = IPTOS_LOWDELAY;
    struct ifreq ifr;
    
    int receive_sock_fd = socket(PF_INET, SOCK_RAW, IPPROTO_UDP); 
    if (receive_sock_fd < 0)
    { 
        perror("Error creating receiving socket.\n"); 
        return -1; 
    }

    const int on = 1; 
    if (setsockopt(receive_sock_fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0)
    {
        perror("Error when setsockopt in ConstructReceiveSockets\n");
        return -1;
    }

    memset(&ifr, 0, sizeof(struct ifreq));
    strcpy(ifr.ifr_name, "eth1");

    if (setsockopt(receive_sock_fd, SOL_SOCKET, SO_BINDTODEVICE,
                       &ifr, sizeof(ifr)) < 0) {
            fprintf(stderr, "SO_BINDTODEVICE failed for eth1");
            perror(" ");
            exit(-1);
    }
    if (setsockopt(receive_sock_fd, SOL_SOCKET, SO_PRIORITY,
                       &tos, sizeof(int)) < 0) {
            perror("Setsockopt SO_PRIORITY failed ");
            exit(-1);
    }


    if (setsockopt(receive_sock_fd, SOL_IP, IP_HDRINCL, &on, sizeof(on)) < 0)
    {
        perror("Error when setsockopt IP_HDRINCL in ConstructReceiveSockets\n");
        return -1;
    }


    // Bind the server.
    struct sockaddr_in serverName = { 0 };
    serverName.sin_family = AF_INET;
    serverName.sin_addr.s_addr = htonl(INADDR_ANY);
    serverName.sin_port = htons(TYPECAST_TA_PORT);

    if (bind(receive_sock_fd, (struct sockaddr *) &serverName, sizeof(serverName)) < 0)
    { 
        perror("bind()\n");
        return -1; 
    }
    
    return receive_sock_fd;
}

int ConstructLBFSockets()
{
    // Initialize socket
    int update_sock_fd = socket(PF_INET, SOCK_DGRAM, 0); 
    if (update_sock_fd < 0)
    { 
        perror("Error creating updating socket.\n"); 
        return -1; 
    }

    // Bind the server.
    struct sockaddr_in serverName = { 0 };
    serverName.sin_family = AF_INET;
    serverName.sin_addr.s_addr = htonl(INADDR_ANY);
    serverName.sin_port = htons(TYPECAST_LBF_PORT);

    if (bind(update_sock_fd, (struct sockaddr *) &serverName, sizeof(serverName)) < 0)
    { 
        perror("bind() update_sock_fd\n");
        return -1; 
    }

    return update_sock_fd;
}


void ReceiveTypeAnnounce(int receive_sock_fd)
{
    int msglen = 0;
    struct sockaddr_in src_addr = { 0 };
    socklen_t addrlen = sizeof(src_addr);
    char buf[MAX_BUFFER_SIZE];

    memset(buf, 0, MAX_BUFFER_SIZE);

    msglen = recvfrom(receive_sock_fd, buf, sizeof(buf), 0, (struct sockaddr *) &src_addr, (socklen_t *) &addrlen);
    if (msglen < 0) 
    { 
        perror("Error when recvfrom.\n");
        exit(-1);
    }

    u_int32_t srcAddr = ntohl(src_addr.sin_addr.s_addr);
    if (srcAddr != get_local_ip_address())
    {
            /* Parse the IP header */
        struct iphdr * ip = (struct iphdr *) buf;
        
        u_int32_t src = ntohl(ip->saddr);
        u_int32_t dst = ntohl(ip->daddr);
        int iph_len = ip->ihl << 2;
        
        struct udphdr *udp = (struct udphdr *) (buf + iph_len);
        
        if (ntohs(udp->dest) != TYPECAST_TA_PORT) { 
            return; 
        }                               

        int len = ntohs(udp->len) - sizeof(struct udphdr);
        printf("Received Type Announce Packet with len = %d\n", len); 
        // Process the packet.
        ProcessTypeAnnouncePacket( buf + iph_len + sizeof(struct udphdr), srcAddr);
        // The following code is to update the hello timer
        int index = name2index("eth1");        
        if (index < 0) { 
            DEBUG(LOG_ERR, 0, "!!!TypeCast Could not get device info!\n");
            return;         
        } else {                 
            neighbor_add(NULL, srcAddr, DEV_NR(index).ifindex); 
        }
    }

}

void UpdateLBF(int update_sock_fd)
{
    int msglen = 0;
    struct sockaddr_in src_addr = { 0 };
    socklen_t addrlen = sizeof(src_addr);
    char buf[MAX_BUFFER_SIZE];

    memset(buf, 0, MAX_BUFFER_SIZE);

    msglen = recvfrom(update_sock_fd, buf, sizeof(buf), 0, (struct sockaddr *) &src_addr, (socklen_t *) &addrlen);
    if (msglen < 0) 
    { 
        perror("Error when recvfrom.\n");
        return;
    }

    ProcessLBFUpdate(buf);
}

void typecast_send_TA() { 
    SendTypeAnnounce(); 
}

int SendTypeAnnounce()
{
    char buf[MAX_BUFFER_SIZE]; 
    memset(buf, 0, MAX_BUFFER_SIZE);

    static int first_time = 1;
    static int seqno = 0; 
    // Initialize seqno to a random number on the first packet.
    if (first_time == 1)
    {
        first_time = 0; 
        seqno = random(); 
    }

    // Aggregate all the ABFs.
    UpdateABFs();

    // Fill the buffer with header and routing information.
    int packet_size = GenerateTypeAnnouncePacket(buf);
         
    // Fill in the entire packet with header now.
    char packet[MAX_BUFFER_SIZE];
    memset(packet, 0, MAX_BUFFER_SIZE);

    struct iphdr *ip;
    struct udphdr *udp;
    int totlen = 0;

    totlen = sizeof(struct iphdr) + sizeof(struct udphdr) + packet_size;

    ip = (struct iphdr *) packet;
    ip->version = IPVERSION;
    ip->ihl = sizeof(struct iphdr) >> 2;
    ip->tos = IPTOS_PREC_NETCONTROL | IPTOS_LOWDELAY; /* Highest priority */
    ip->tot_len = htons(totlen);
    ip->id = 0; /* Let IP set */
    ip->frag_off = htons(0x4000); /* Set Do Not Fragment bit */
    ip->ttl = 1;    // !!!! TODO !!!! What do I set this to?
    ip->protocol = IPPROTO_UDP;
    ip->saddr = htonl(get_local_ip_address());  // !!!!! TODO !!!! Is this correct?
    ip->daddr = htonl(0xFFFFFFFF);

    ip->check = 0; /* Let kernel calculate */

    udp = (struct udphdr *) (packet + sizeof(struct iphdr));

    udp->source = htons(TYPECAST_TA_PORT);
    udp->dest = htons(TYPECAST_TA_PORT);
    udp->len = htons(packet_size + sizeof(struct udphdr));

    udp->check = in_udp_csum(ip->saddr, ip->daddr, udp->len, 
        ip->protocol, (unsigned short *)udp);

    memcpy(packet + sizeof(struct iphdr) + sizeof(struct udphdr),
           buf, packet_size);

    // Set up broadcast destination
    struct sockaddr_in dst_addr; 
    memset(&dst_addr, 0, sizeof(dst_addr));
    dst_addr.sin_family = AF_INET; 
    dst_addr.sin_addr.s_addr = htonl(0xFFFFFFFF); 
    dst_addr.sin_port = htons(TYPECAST_TA_PORT); 

    printf("Sending Type Announce Packet with size = %d\n", packet_size); 
    ssize_t msglen = 0; 
    msglen = sendto(typecast_TA_sock, packet, totlen, 0, (struct sockaddr *) &dst_addr, sizeof(dst_addr) );
    if (msglen < 0) 
    { 
        perror("Error when sendto:"); 
        exit( -1); // Too bad if it fails.
    }

    printf("sendto: sent with msglen = %d.\n", msglen);

    return 0;
}



